🎖️GitЯра🎖️
Commit e2439a492a7c0377c1d9fe52524f2c8e05fafdec
Parents : 2f19186
Author : James Rich <2199651+jamesarich@users.noreply.github.com>
Signature : Signature validation error
Date : 2026-08-01T17:09:06-05:00
Committer : GitHub <noreply@github.com>
Date : 2026-08-01T22:09:06Z
fix(desktop): stop SIGSEGV on every Linux notification (#6543)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Changes
1 files changed, 17 insertions(+), 17 deletions(-)
Diff
diff --git a/desktopApp/src/main/kotlin/org/meshtastic/desktop/notification/LinuxNotificationSender.kt b/desktopApp/src/main/kotlin/org/meshtastic/desktop/notification/LinuxNotificationSender.kt
index 40f0da326c..e8a90eeb7c 100644
--- a/desktopApp/src/main/kotlin/org/meshtastic/desktop/notification/LinuxNotificationSender.kt
+++ b/desktopApp/src/main/kotlin/org/meshtastic/desktop/notification/LinuxNotificationSender.kt
@@ -29,6 +29,17 @@ import org.meshtastic.core.repository.Notification
*
* Only the minimal API surface needed for fire-and-forget desktop notifications is exposed. See:
* https://developer-old.gnome.org/libnotify/stable/
+ *
+ * **The GLib entry points below MUST stay on this interface.** They are deliberately resolved through the *libnotify*
+ * handle rather than a separate `Native.load("gobject-2.0", ...)`. `dlsym()` on a library handle searches that library
+ * *and its dependency chain*, and libnotify has a `DT_NEEDED` on `libgobject-2.0.so.0`/`libglib-2.0.so.0` — so this
+ * guarantees we get the exact same GLib instance that libnotify itself is bound to.
+ *
+ * Loading `gobject-2.0` separately is NOT safe: a JVM process can easily have two distinct GLib copies mapped at once
+ * (e.g. the JDK/Compose Desktop AWT stack drags in a bundled GTK3 + GLib, while libnotify binds to the system GLib).
+ * Each copy owns a private `GType` registry, so freeing a `NotifyNotification` created by one copy with the other
+ * copy's `g_object_unref()` walks the wrong registry and jumps through a null vtable slot — an immediate SIGSEGV at
+ * `pc=0x0` inside `g_object_unref`, not a catchable exception.
*/
@Suppress("FunctionNaming", "FunctionParameterNaming", "ktlint:standard:function-naming")
private interface LibNotify : Library {
@@ -45,11 +56,9 @@ private interface LibNotify : Library {
fun notify_notification_show(notification: Pointer, error: PointerByReference?): Boolean
fun notify_uninit()
-}
-/** Minimal GLib bindings for GVariant creation and GObject ref-counting. */
-@Suppress("FunctionNaming", "FunctionParameterNaming", "ktlint:standard:function-naming")
-private interface GLib : Library {
+ // --- GLib, resolved via libnotify's own dependency chain. See the KDoc above before touching these. ---
+
fun g_object_unref(obj: Pointer)
fun g_variant_new_boolean(value: Boolean): Pointer
@@ -97,28 +106,22 @@ class LinuxNotificationSender(
) : NativeNotificationSender {
private val lib: LibNotify?
- private val glib: GLib?
init {
var loadedLib: LibNotify? = null
- var loadedGLib: GLib? = null
try {
loadedLib = Native.load("notify", LibNotify::class.java) as LibNotify
- loadedGLib = Native.load("gobject-2.0", GLib::class.java) as GLib
if (loadedLib.notify_init(appName)) {
Logger.i { "libnotify initialized for '$appName'" }
} else {
Logger.w { "notify_init('$appName') returned false" }
loadedLib = null
- loadedGLib = null
}
} catch (e: UnsatisfiedLinkError) {
Logger.w(e) { "libnotify not available — native Linux notifications disabled" }
loadedLib = null
- loadedGLib = null
}
lib = loadedLib
- glib = loadedGLib
}
/** Whether libnotify was successfully loaded and initialized. */
@@ -151,7 +154,7 @@ class LinuxNotificationSender(
}
shown
} finally {
- glib?.g_object_unref(ptr)
+ libnotify.g_object_unref(ptr)
}
}
@@ -177,14 +180,11 @@ class LinuxNotificationSender(
// desktop-entry hint associates notifications with the app's .desktop file,
// enabling proper icon resolution and notification grouping by the daemon.
- glib?.let { g ->
- libnotify.notify_notification_set_hint(ptr, "desktop-entry", g.g_variant_new_string(desktopEntry))
- }
+ // The GVariants below are floating refs; notify_notification_set_hint() sinks them, so we must not unref.
+ libnotify.notify_notification_set_hint(ptr, "desktop-entry", libnotify.g_variant_new_string(desktopEntry))
if (notification.isSilent) {
- glib?.let { g ->
- libnotify.notify_notification_set_hint(ptr, "suppress-sound", g.g_variant_new_boolean(true))
- }
+ libnotify.notify_notification_set_hint(ptr, "suppress-sound", libnotify.g_variant_new_boolean(true))
}
}
}
Served by rngit 1.5.2 - Generated in 0.06s